//-----------------------------------------------------------------------------
// MenuScreen.cs
//
// XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using Ferpect.GameState.MenuItems;
using Ferpect.GameComponents.Design;

namespace Ferpect.GameState
{
    /// <summary>
    /// Base class for screens that contain a menu of options. The user can
    /// move up and down to select an entry, or cancel to back out of the screen.
    /// </summary>
    [DesignerSerializer(MenuScreen.MenuScreenTypeSerializer, typeof(TypeCodeDomSerializer))]
    [DesignerSerializer(MenuScreen.MenuScreenControlSerializer, typeof(CodeDomSerializer))]
    [ViewDocumentDesigner(MenuScreen.MenuScreenDesigner)]
    public class MenuScreen : GameScreen
    {
        private MenuItemCollection menuItems;
        private int selectedEntry = 0;
        private string menuTitle;
        private const string MenuTitleDefault = "Menu Title";
        private string fontName;
        private SpriteFont font;
        private Color textColor;
        private Color focusTextColor;

        internal const string MenuScreenTypeSerializer = "Ferpect.GameComponents.Design.Serialization.ViewTypeCodeDomSerializer`1[[Ferpect.GameState.Design.MenuScreenControl, " + AssemblyRefs.GameStateDesign + "]]";
        internal const string MenuScreenControlSerializer = "Ferpect.GameComponents.Design.Serialization.ViewControlCodeDomSerializer`2[[Ferpect.GameState.Design.MenuScreenControl, " + AssemblyRefs.GameStateDesign + "], [Ferpect.GameState.MenuScreen, " + AssemblyRefs.GameState + "]]";
        internal const string MenuScreenDesigner = "Ferpect.GameState.Design.MenuScreenDesigner, " + AssemblyRefs.GameStateDesign;

        /// <summary>
        /// Constructor.
        /// </summary>
        public MenuScreen()
        {
            this.menuItems = new MenuItemCollection(this);
            this.menuTitle = MenuTitleDefault;
            this.fontName = String.Empty;
            this.TransitionOnSeconds = 0.5;
            this.TransitionOffSeconds = 0.5;
            this.textColor = Color.White;
            this.focusTextColor = Color.Yellow;
        }

        [Category("Appearance")]
        [DefaultValue("")]
        [DisplayName("Font")]
        [TypeConverter(typeof(SpriteFontConverter))]
        public string FontName
        {
            get { return this.fontName; }
            set { this.fontName = value; }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SpriteFont Font
        {
            get
            {
                if (this.font == null)
                {
                    return this.ScreenManager.Font;
                }
                return this.font;
            }
            set
            {
                this.font = value;
            }
        }

        [Category("Appearance")]
        [DisplayName("Text Color (Default)")]
        [Description("Specifies the default color of menu item text.")]
        public Color TextColor
        {
            get { return this.textColor; }
            set { this.textColor = value; }
        }

        [Category("Appearance")]
        [DisplayName("Text Color (Focus)")]
        [Description("Specifies the color of menu item text when the menu item has focus.")]
        public Color FocusTextColor
        {
            get { return this.focusTextColor; }
            set { this.focusTextColor = value; }
        }


        /// <summary>
        /// Gets the list of menu entries, so derived classes can add
        /// or change the menu contents.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public IList<MenuItem> MenuItems
        {
            get { return this.menuItems; }
        }

        public MenuItem MenuItemFromPoint(Point pt)
        {
            foreach (MenuItem item in this.menuItems)
            {
                if (item.HitTest(pt))
                {
                    return item;
                }
            }
            return null;
        }

        [Category("Appearance")]
        [DefaultValue(MenuTitleDefault)]
        public virtual string Text
        {
            get { return this.menuTitle; }
            set { this.menuTitle = value; }
        }

        protected override void LoadContent()
        {
            if (!String.IsNullOrEmpty(this.fontName))
            {
                // For this to work, the Content.RootDirectory property must be set
                // to the same value as the content project that builds this font.
                this.font = this.Content.Load<SpriteFont>(this.fontName);
            }
            base.LoadContent();
        }

        protected override void UnloadContent()
        {
            this.font = null;
            base.UnloadContent();
        }

        /// <summary>
        /// Responds to user input, changing the selected entry and accepting
        /// or cancelling the menu.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input.MenuUp)
            {
                if (selectedEntry <= 0)
                {
                    this.SetFocus(menuItems.Count - 1);
                }
                else
                {
                    this.SetFocus(selectedEntry - 1);
                }
            }
            else if (input.MenuDown)
            {
                if (selectedEntry >= menuItems.Count - 1)
                {
                    this.SetFocus(0);
                }
                else
                {
                    this.SetFocus(selectedEntry + 1);
                }
            }

            // Accept or cancel the menu?
            if (input.MenuSelect)
            {
                this.OnSelectEntry();
            }
            else if (input.MenuCancel)
            {
                this.OnCancel();
            }
        }

        protected void SetFocus(int itemIndex)
        {
            if (itemIndex != this.selectedEntry)
            {
                this.menuItems[this.selectedEntry].OnLeave();
                this.menuItems[itemIndex].OnEnter();
                this.selectedEntry = itemIndex;
            }
        }

        /// <summary>
        /// Handler for when the user has chosen a menu entry.
        /// </summary>
        protected virtual void OnSelectEntry()
        {
            this.menuItems[selectedEntry].OnSelected();
        }

        /// <summary>
        /// Handler for when the user has cancelled the menu.
        /// </summary>
        protected virtual void OnCancel()
        {
            this.ExitScreen();
        }

        /// <summary>
        /// Helper overload makes it easy to use OnCancel as a MenuEntry event handler.
        /// </summary>
        protected void OnCancel(object sender, EventArgs e)
        {
            this.OnCancel();
        }

        internal void OnItemAdded(MenuItem item)
        {
            if (this.menuItems.Count == 1)
            {
                this.menuItems[0].OnEnter();
            }
        }

        /// <summary>
        /// Updates the menu.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            bool active = this.IsActive;

            // Update each nested MenuEntry object.
            for (int i = 0; i < menuItems.Count; i++)
            {
                menuItems[i].Update(this, active, gameTime);
            }
        }

        /// <summary>
        /// Draws the menu.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            Vector2 position = new Vector2(100, 150);

            // Make the menu slide into place during transitions, using a
            // power curve to make things look more interesting (this makes
            // the movement slow down as it nears the end).
            float transitionOffset = (float)Math.Pow(TransitionPosition, 2);

            if (ScreenState == ScreenState.TransitionOn)
                position.X -= transitionOffset * 256;
            else
                position.X += transitionOffset * 512;

            spriteBatch.Begin();

            bool active = this.IsActive;

            // Draw each menu entry in turn.
            for (int i = 0; i < menuItems.Count; i++)
            {
                MenuItem menuEntry = menuItems[i];
                menuEntry.Draw(position, active, gameTime);

                position.Y += menuEntry.GetHeight(this);
            }

            // Draw the menu title.
            Vector2 titlePosition = new Vector2(426, 80);
            Vector2 titleOrigin = this.Font.MeasureString(menuTitle) / 2;
            Color titleColor = new Color(192, 192, 192, TransitionAlpha);
            float titleScale = 1.25f;

            titlePosition.Y -= transitionOffset * 100;

            spriteBatch.DrawString(this.Font, menuTitle, titlePosition, titleColor, 0, titleOrigin, titleScale, SpriteEffects.None, 0);

            spriteBatch.End();
        }
    }
}
